# Copyright 2012 Robert Muth
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 3
# of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""
HocusPocus Plugin "alarm"
"""

# python imports
import datetime
import logging
import threading
import time

import tornado.options
import tornado.web

# local imports
import utils
import vlc_driver

# ======================================================================

tornado.options.define(
    "alarm_ring_media",
    default="NO-ALARM-MEDIA-SET",
    type=str,
    help="alarm_plugin: play this media file/playlist as an alarm")

tornado.options.define(
    "alarm_current_time_format",
    default="%A %H:%M",
    type=str,
    help="alarm pluging: format for current time")

tornado.options.define(
    "alarm_alarm_time_format",
    default="%A %H:%M",
    type=str,
    help="alarm pluging: format for alarm time")

OPTIONS = tornado.options.options

# ======================================================================

def CheckedOrEmpty(x):
    if x:
        return "checked"
    else:
        return ""

class Alarm:
    """Represents a single Alarm, e.g.
       sound alarm at 8am on Mondays and Wednesdays
    """
    def __init__(self):
        self.h = 0
        self.m = 0
        self.days = [False] * 7
        return

    def __str__(self):
        return "%d:%d %s" % (self.h, self.m, str(self.days))

    def MakeBlob(self, no):
        obj = utils.Datablob()
        obj.no = no
        obj.h = self.h
        obj.m = self.m
        obj.day0 = CheckedOrEmpty(self.days[0])
        obj.day1 = CheckedOrEmpty(self.days[1])
        obj.day2 = CheckedOrEmpty(self.days[2])
        obj.day3 = CheckedOrEmpty(self.days[3])
        obj.day4 = CheckedOrEmpty(self.days[4])
        obj.day5 = CheckedOrEmpty(self.days[5])
        obj.day6 = CheckedOrEmpty(self.days[6])
        return obj

    def ComputeNextAlarm(self, dt):
        oneday = datetime.timedelta(1)
        al = datetime.datetime(dt.year, dt.month, dt.day, self.h, self.m)
        if al <= dt:
            al += oneday
        assert al > dt
        for _ in range(7):
            weekday = al.weekday()
            if self.days[weekday]:
                return [al]
            al += oneday
        return []

    def Update(self, req):
        def GetBool(t):
            return req.get_argument(t, "MISSING") != "MISSING"

        self.days[0] =  GetBool("day0")
        self.days[1] =  GetBool("day1")
        self.days[2] =  GetBool("day2")
        self.days[3] =  GetBool("day3")
        self.days[4] =  GetBool("day4")
        self.days[5] =  GetBool("day5")
        self.days[6] =  GetBool("day6")
        self.h = int(req.get_argument("h"))
        self.m = int(req.get_argument("m"))
        logging.info("new alarm: [%s]", str(self))



class AlarmManager:
    def AlarmCheckerThread(self):
        logging.info("alarm thread started")
        while self._keep_going:

            time.sleep(self._interval)
            if not self._next_alarm:
                continue
            now = datetime.datetime.now()
            if now < self._next_alarm:
                continue
            self.StartAlarm()


    def ComputeNextAlarm(self):
        now = datetime.datetime.now()
        next_alarms = []
        for a in self._alarms:
            next_alarms += a.ComputeNextAlarm(now)
        if not next_alarms:
            return None
        else:
            return min(next_alarms)

    def GetNextAlarm(self):
        return self._next_alarm

    def StartAlarm(self):
        logging.info("start alarm")
        self._next_alarm = None
        vlc_driver.VlcStart(OPTIONS.alarm_ring_media)

    def StopAlarm(self):
        logging.info("stop alarm")
        vlc_driver.VlcRunCommand("stop")
        self._next_alarm = self.ComputeNextAlarm()


    def __init__(self, no, interval, start_cmd, stop_cmd):
        self._alarms = [Alarm() for _ in range(no)]
        self._start_cmd = start_cmd
        self._stop_cmd = stop_cmd
        # used for shuting down the thread
        self._keep_going = True
        self._next_alarm = None
        # sleep this many seconds before checking whether it is time for an
        # and alarm
        self._interval = interval
        self._thread = threading.Thread(target=self.AlarmCheckerThread)
        self._thread.setDaemon(True)
        self._thread.start()


    def MakeDataBlobs(self):
        return [a.MakeBlob(no) for (no, a) in enumerate(self._alarms)]

    def Update(self, req):
        no = int(req.get_argument("no"))
        assert no < len(self._alarms)
        self._alarms[no].Update(req)
        self._next_alarm = self.ComputeNextAlarm()




ALARM_MANAGER = AlarmManager(4,
                             10,
                             "",
                             "",
                             )

class Handler(tornado.web.RequestHandler):
    """Handles the display of the alarm plugin page
    """
    def initialize(self, template_args):
        logging.debug("alarm hander init")
        self._template_args = template_args

    @tornado.web.asynchronous
    def get(self, dummy_p, *dummy_k):
        logging.info("alarm handler request")
        self._template_args["alarms"] = ALARM_MANAGER.MakeDataBlobs()
        self.write(self.render_string("alarm.html", **self._template_args))
        self.finish()

class CommandHandler(tornado.web.RequestHandler):
    """Handles ajax request made by the alarm plugin page
    """

    def initialize(self, template_args):
        logging.debug("alarm command handler init")
        self._template_args = template_args


    @tornado.web.asynchronous
    def get(self, dummy_p, *dummy_k):
        command = self.get_argument("command")
        logging.info("alarm command handler request [%s]", command)
        if command == "refresh":
            pass
        elif command == "test":
            ALARM_MANAGER.StartAlarm()
        elif command == "stop":
            ALARM_MANAGER.StopAlarm()
        elif command == "update":
            ALARM_MANAGER.Update(self)
        else:
            logging.error("unknown command [%s]", command)
        now = datetime.datetime.now()
        now_str =  "Current Time: " + now.strftime(OPTIONS.alarm_current_time_format)
        next_alarm_str = ""
        delta_str = ""
        next_alarm = ALARM_MANAGER.GetNextAlarm()
        if next_alarm:
            next_alarm_str = "Next Alarm: " + next_alarm.strftime(OPTIONS.alarm_alarm_time_format)
            delta = next_alarm - now
            delta = datetime.timedelta(delta.days, delta.seconds, 0)
            delta_str = "(Time until then " + str(delta) + ")"
        else:
            next_alarm_str = "no alarm active"
        status = now_str + "  " + next_alarm_str + "  " + delta_str
        logging.info("alarm status [%s]", status)
        self.write(status)
        self.finish()


def GetHandlers():
    return [(r"/alarm/(.*)", Handler),
            (r"/alarm_command(.*)", CommandHandler),
            ]

def GetDependencies():
    return []


